<?php

class SessionException extends Exception
{
}

class User
{

	private static function getPassword () {
		if (!isset ($_COOKIE['random']) || !self::authenticated ()) {
			return false;
		}
		return Encryption::decryptAES ($_SESSION['encrypted'], base64_decode ($_COOKIE['random']));
	}

	static function getAuthenticated () {
		$uid = self::authenticated ();
		if ($uid === false) {
			return false;
		}
		
		$user = self::getUserById ($uid);
		
		$password = self::getPassword ();
		if ($password === false) {
			throw new SessionException ('Session or cookie not available');
		}
		
		// Decrypt key
		$user['private_clear'] = Encryption::changePassphrase ($user['private'], $password);
		
		return $user;
	}

	static function getUserById ($uid) {
		$stmt = DB::get ()->prepare ('SELECT * FROM users WHERE uid=:uid LIMIT 1;');
		$stmt->execute (array (':uid'=>$uid));
		$result = $stmt->fetchAll ();
		return (sizeof ($result) == 0) ? false : $result[0];
	}

	static function authenticated () {
		return (isset ($_SESSION['uid'])) ? $_SESSION['uid'] : false;
	}

	static function authenticate ($username, $password) {
		// We won't catch PDOExceptions here, we'll let the calling code do it.
/*		try {*/
			$stmt = DB::get ()->prepare ("SELECT uid FROM `users` WHERE `username`=:username AND `password`=:password LIMIT 1;");
			$stmt->execute (array ('username'=>$username, 'password'=>sha1($password.$username, true)));
			$results = $stmt->fetchAll ();
/*		} catch (PDOException $e) {
			$errors['database_error'] = $e->getMessage ();
			return false;
		}*/

		return (sizeof ($results) > 0) ? $results[0]['uid'] : false;
	}
	
	static function login ($username, $password) {
		$uid = self::authenticate ($username, $password);
		if ($uid !== false) {
			$_SESSION['uid'] = $uid;
			
			/* Generate a really random string, which will secure their password in the session data.
			 * The random number will reside in a cookie on their system.
			 * Yes, this is comprimisable if the browser and/or webserver are broken.
			 * We're treating those as acceptable.
			 * This way there is no non-volatile record of their password in cleartext on the server.
			 */
			
			// Generate a 256bit (binary) string as a key for AES256.
			$rand = '';
			for ($i = 0; $i < 32; $i++) {
				$rand .= chr (mt_rand (0x00, 0xFF));
			}
			setcookie ('random', base64_encode ($rand));
			
			$_SESSION['encrypted'] = Encryption::encryptAES ($password, $rand);
			
			return true;
		}
		return false;
	}
	
	static function create ($username, $password, $firstname, $lastname, $email, &$private_clear, &$errors) {
		if (!preg_match ('/^[0-9a-zA-Z\-_\.]{4,}$/', $username)) {
			$errors['bad_username'] = true;
		}

		if (strlen ($password) < 6 ||
			!preg_match ('/[A-Z]/', $password) ||
			!preg_match ('/[a-z]/', $password) ||
			!preg_match ('/[^a-zA-Z]/', $password)) {
			$errors['bad_password'] = true;
		}
		
		if (strlen (trim ($firstname)) < 2) {
			$errors['bad_firstname'] = true;
		}
		
		if (strlen (trim ($lastname)) < 2) {
			$errors['bad_lastname'] = true;
		}
		
		if (!preg_match ('/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $email)) {
			$errors['bad_email'] = true;
		}
		
		if (sizeof ($errors) == 0) {
			try {
				Encryption::newPair ($private_clear, $public);
				$private = Encryption::changePassphrase ($private_clear, null, $password);
				$stmt = DB::get ()->prepare ("INSERT INTO `users` (username, password, email, firstname, lastname, private, public, last_login) VALUES (:username, :password, :email, :firstname, :lastname, :private, :public, NOW());");
				$stmt->execute (array ('username'=>$username, 'password'=>sha1($password.$username, true), 'email'=>$email, 'firstname'=>$firstname, 'lastname'=>$lastname, 'private'=>$private, 'public'=>$public));
			} catch (PDOException $e) {
				if ($stmt->errorCode () == 23000 && preg_match ('/(email|username)/', $e->getMessage (), $matches)) {
					if ($matches[1] == 'email') {
						$errors['registered_email'] = true;
						$errors['bad_email'] = true;
					} else {
						$errors['registered_username'] = true;
						$errors['bad_username'] = true;
					}
				} else {
					throw $e;
				}
			}
		}
		
		return (sizeof ($errors) == 0);
	}
	
}
